package com.gjt.dlmall.product.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.gjt.dlmall.product.vo.Category2Vo;
import com.gjt.dlmall.product.vo.Category3Vo;
import com.gjt.dlmall.product.vo.CategoryVo;
import org.apache.commons.lang.StringUtils;
import org.checkerframework.checker.units.qual.A;
import org.checkerframework.checker.units.qual.C;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.gjt.common.utils.PageUtils;
import com.gjt.common.utils.Query;

import com.gjt.dlmall.product.dao.CategoryDao;
import com.gjt.dlmall.product.entity.CategoryEntity;
import com.gjt.dlmall.product.service.CategoryService;


@Service("categoryService")
public class CategoryServiceImpl extends ServiceImpl<CategoryDao, CategoryEntity> implements CategoryService {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    @Autowired
    private RedissonClient redissonClient;
    private List<CategoryEntity> entitis;

    @Override
    public PageUtils queryPage(Map<String, Object> params) {
        IPage<CategoryEntity> page = this.page(
                new Query<CategoryEntity>().getPage(params),
                new QueryWrapper<CategoryEntity>()
        );

        return new PageUtils(page);
    }

    /**
     * 查询所有分类
     * @return
     */
    @Override
    public List<CategoryEntity> listwithTree() {
        //1.查询所有分类
        List<CategoryEntity> entitis = baseMapper.selectList(null);
        //2.组装成父子的树形结构

        //2.1 找到所有的一级分类
        //过滤
        List<CategoryEntity> levelOneMenus = entitis.stream().filter(
                //过滤一级分类 parentId=0 根据这个条件可以构建出所有的一级分类的数据
                categoryEntity ->  categoryEntity.getParentId() == 0

        ).map((menu)->{
            //出现递归操作，关联出子分类（二、三级分类）
            menu.setChildrens(getChildrens(menu, entitis));
            return menu;
        }).collect(Collectors.toList());
        return levelOneMenus;
    }

    /**
     * 逻辑批量删除
     * @param ids 分类id
     */
    @Override
    public void removeMenuByIds(List<Integer> ids) {
        //TODO 检查当前要删除的分类是否被引用
        //逻辑删除
        baseMapper.deleteBatchIds(ids);
    }

    /**
     * 收集三级分类菜单的id
     * @param categoryId
     * @return
     */
    @Override
    public Long[] findCategoryIds(Integer categoryId) {
        //创建空数组
        List<Long> paths = new ArrayList<>();
        //通过递归查询到当前分类 与 父分类ID 添加到 paths 集合中
        List<Long> parenPath = findParentPath(categoryId,paths);
        //反转
        Collections.reverse(parenPath);
        return parenPath.toArray(new Long[parenPath.size()]);
    }
    /**
     * 使用springCache 改造三级分类
     * @return
     */
    @Cacheable(value = "category", key = "#root.method.name")
    public List<CategoryVo> getLevel1Categorys(){
        List<CategoryVo> categoryJsonFromDb = getCategoryJsonFromDb();
        return categoryJsonFromDb;

    }
//    /**
//     * 使用redis 改造三级分类
//     * @return
//     */
//    @Cacheable(value = "category", key = "#root.method.name")
//    public List<CategoryVo> getLevel1Categorys(){
//        //1、从缓存中查询数据
//        String categoryJSON = stringRedisTemplate.opsForValue().get("categoryJSON");
//        if (StringUtils.isEmpty(categoryJSON)){
//            //2/缓存中没有数据，查询数据库，从数据库中查询分类数据
//            List<CategoryVo> categoryJsonFromDb = getCategoryJsonFromWithRedissonLock();
////            //3、将查询到的数据放入到缓存中，将对象转换为json传入redis
////            stringRedisTemplate.opsForValue().set("categoryJSON",JSON.toJSONString(categoryJsonFromDb));
//
//            return categoryJsonFromDb;
//        }
//        //4、如果缓存中有数据，将查询出的数据转成java对象，指明转为对象类型
//        List<CategoryVo> categoryVos = JSON.parseObject(categoryJSON, new TypeReference<List<CategoryVo>>() {
//        });
//        return categoryVos;
//
//    }

    /**
     * 三级菜单的级联更新（解决缓存一致性）
     * @param category
     */
    @CacheEvict(value = "category",key = "'getLevel1Categorys'")
    @Override
    public void updateCascade(CategoryEntity category) {
        this.updateById(category);
    }

    /**
     * 获取三级分类（redisson分布式锁）
     * @return
     */
    public List<CategoryVo> getCategoryJsonFromWithRedissonLock() {
        String uuid = UUID.randomUUID().toString();
        //1、占分布式
        RLock lock = redissonClient.getLock("categoryJSON-lock");
        lock.lock(); //加锁
        List<CategoryVo> dataFromDb=null;
        try{
            dataFromDb =getDataFromDb();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();//释放锁
        }
        return dataFromDb;

    }
    /**
     * 获取三级分类（redis分布式锁）
     * @return
     */
    public List<CategoryVo> getCategoryJsonFromWithRedisLock() {
        String uuid = UUID.randomUUID().toString();
        //1、占分布式
        Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent("lock", uuid,30,TimeUnit.SECONDS);
        if (lock){
            //加锁成功，执行业务
            //stringRedisTemplate.expire("lock",30,TimeUnit.SECONDS); // 设置过期时间30秒
            //List<CategoryVo> dataFromDb = getDataFromDb();
            //删除锁
           /* String lock1 = stringRedisTemplate.opsForValue().get("lock");
            if (uuid.equals(lock1)){
                //删除自己的锁
                stringRedisTemplate.delete("lock");
            }*/
            List<CategoryVo> dataFromDb = null;
           try{
               dataFromDb = getDataFromDb();

           }finally {
               //删除锁
               String script = "if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";
               stringRedisTemplate.execute(new DefaultRedisScript<Integer>(script,Integer.class),Arrays.asList("lock"),uuid);
           }
            return dataFromDb;
        }else{
            //加锁失败，间隔100ms 重试
            return getCategoryJsonFromWithRedisLock();//自旋
        }
    }

    private List<CategoryVo> getDataFromDb() {
        //得到锁之后，去查看缓存中是否有这个数据，如果没有数据，继续查询数据库
        String categoryJSON = stringRedisTemplate.opsForValue().get("categoryJSON");
        if (!StringUtils.isEmpty(categoryJSON)) {
            List<CategoryVo> categoryVos = JSON.parseObject(categoryJSON, new TypeReference<List<CategoryVo>>() {
            });
            return categoryVos;
        }
        //改为查询所有分类
        List<CategoryEntity> selectList = baseMapper.selectList(null);

        List<CategoryEntity> categoryEntities = getParent_id(selectList, 0);
        List<CategoryVo> categoryVoList = categoryEntities.stream().map(categoryEntity -> {
            CategoryVo categoryVo = new CategoryVo();
            BeanUtils.copyProperties(categoryEntity, categoryVo);
            //查询一级分类下的二级分类
            //改造前 baseMapper.selectList(new QueryWrapper<CategoryEntity>().eq("parent_id", categoryEntity.getId()));
            List<CategoryEntity> level2Categorys = getParent_id(selectList, categoryEntity.getId());
            //将当前一级分类下的二级分类封装成VO
            if (level2Categorys != null) {
                List<Category2Vo> category2VoList = level2Categorys.stream().map(level2Category -> {
                    Category2Vo category2Vo = new Category2Vo();
                    category2Vo.setId(level2Category.getId().toString());
                    category2Vo.setName(level2Category.getName());
                    category2Vo.setCategory1Id(categoryEntity.getId().toString());
                    //查询二级分类下的三级分类信息
                    //改造前 baseMapper.selectList(new QueryWrapper<CategoryEntity>().eq("parent_id", category2Vo.getId()));
                    List<CategoryEntity> level3Categorys = getParent_id(selectList, level2Category.getId());
                    //将当前的二级分类下的三级分类封装到VO中
                    if (level3Categorys != null) {
                        List<Category3Vo> category3VoList = level3Categorys.stream().map(level3Category -> {
                            Category3Vo category3Vo = new Category3Vo();
                            category3Vo.setId(level3Category.getId().toString());
                            category3Vo.setName(level3Category.getName());
                            category3Vo.setCategory2Id(category2Vo.getId());
                            return category3Vo;
                        }).collect(Collectors.toList());
                        category2Vo.setCategory3VoList(category3VoList);
                    }
                    return category2Vo;
                }).collect(Collectors.toList());
                categoryVo.setCategory2VoList(category2VoList);
            }
            return categoryVo;
        }).collect(Collectors.toList());
        //3、将查询到的数据放入到缓存中，将对象转换为json传入redis
        stringRedisTemplate.opsForValue().set("categoryJSON", JSON.toJSONString(categoryVoList), 1, TimeUnit.DAYS);


        return categoryVoList;
    }

    /**
     * 获取三级分类(本地锁)
     * @return
     */
    public List<CategoryVo> getCategoryJsonFromDb() {
        synchronized (this){
            //得到锁之后，去查看缓存中是否有这个数据，如果没有数据，继续查询数据库
            return getDataFromDb();
        }

    }

    private List<CategoryEntity> getParent_id(List<CategoryEntity> selectList,Integer parentId) {
        List<CategoryEntity> collect = selectList.stream().filter(item -> {
            return item.getParentId() == parentId;
        }).collect(Collectors.toList());

        return collect;
    }

    /**
     * 递归收集菜单id
     * @param categoryId 分类id
     * @param paths
     * @return [560,569,568]
     */
    private List<Long> findParentPath(Integer categoryId, List<Long> paths) {
        //收集当前分类id放到集合中
        paths.add(categoryId.longValue());
        CategoryEntity categoryEntity = this.getById(categoryId);
        if (categoryEntity.getParentId()!=0){
            findParentPath(categoryEntity.getParentId(),paths);
        }
        return paths;
    }

    /**
     * 递归查找指定分类中的子分类信息(所有菜单的子菜单)
     * @param currentMenu
     * @param entitis
     * @return
     */
    private List<CategoryEntity> getChildrens(CategoryEntity currentMenu, List<CategoryEntity> entitis) {
        List<CategoryEntity> childrens = entitis.stream().filter(
                //过滤当前菜单中所有的子菜单 currentMenu.getId() == categoryEntity.getParentId();
                categoryEntity -> currentMenu.getId().equals(categoryEntity.getParentId())
        ).map((menu)-> {
            //获取子分类
            menu.setChildrens(getChildrens(menu, entitis));
            return menu;
        }).collect(Collectors.toList());
        return childrens;
    }

}